package org.fing.edu.uy.esbadp.modules.adpMotorMonitor.wsdl;

import java.net.URL;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;

import javax.xml.namespace.QName;

import org.fing.edu.uy.esbadp.core.compare.wsdl.OperationDiff;
import org.fing.edu.uy.esbadp.core.compare.wsdl.ParameterDiff;
import org.fing.edu.uy.esbadp.core.compare.wsdl.WsdlDiff;
import org.fing.edu.uy.esbadp.core.interfaces.ICompareWsdl;
import org.fing.edu.uy.esbadp.logger.LoggerADP;
import org.ow2.easywsdl.wsdl.WSDLFactory;
import org.ow2.easywsdl.wsdl.api.Binding;
import org.ow2.easywsdl.wsdl.api.BindingOperation;
import org.ow2.easywsdl.wsdl.api.Description;
import org.ow2.easywsdl.wsdl.api.Operation;
import org.ow2.easywsdl.wsdl.api.WSDLReader;
import org.ow2.easywsdl.wsdl.api.WSDLWriter;

public class CompareWsdl implements ICompareWsdl {

    private static final java.util.logging.Logger logger = LoggerADP.getLogger(CompareWsdl.class.getSimpleName());
    
    private final String EXPR_STYLE_DOCUMENT = ".*<.*binding\\s+.*style=\"document\".*/>.*";

    @Override
    public WsdlDiff compareDefinitions(URL url0, URL url1) {
        WsdlDiff wsdlDiff = new WsdlDiff();
        try {
            if (logger.isLoggable(Level.FINE)) {
                logger.log(Level.FINE, "wsdl compare: [{0}] to  [{1}]", new Object[]{url0, url1});
            }            
            
            WSDLWriter writer = WSDLFactory.newInstance().newWSDLWriter();
            WSDLReader reader = WSDLFactory.newInstance().newWSDLReader();

            Description defs0 = reader.read(url0);
            Description defs1 = reader.read(url1);
                        
            HashSet<String> processedWsdl0 = new HashSet<String>();
            HashSet<String> unProcessedWsdl1 = new HashSet<String>();
            
            //set namespace of wsdl elements
            String namespaceURI = defs1.getTargetNamespace();
            String prefix = defs1.getNamespaces().getPrefix(namespaceURI);
            wsdlDiff.setNameSpace(new QName(namespaceURI, "", prefix));

            for (Binding wsdl_bind1 : defs1.getBindings()) {
                Binding wsdl_bind0 = defs0.getBinding(wsdl_bind1.getQName());
                boolean isDocumentStyle = isDocumentStyle(writer.writeWSDL(defs1));
                
                //COMPARE OPERATION WITH NO CHANGE IN NAME OPERATION
                for (BindingOperation op_bind : wsdl_bind1.getBindingOperations()) {
                    Operation op1 = op_bind.getOperation();
                    
                    Operation op0 = wsdl_bind0.getBindingOperation(op1.getQName().getLocalPart()) != null ? wsdl_bind0.getBindingOperation(op1.getQName().getLocalPart()).getOperation() : null;

                    if (op0 != null) {
                        //agrego al conjunto de operaciones procesadas
                        processedWsdl0.add(op1.getQName().getLocalPart());
                        List<ParameterDiff> parametersDiff;

                        //comparo differencias a nivel de parametros
                        if (isDocumentStyle) {
                            parametersDiff = CompareDocument.compareOperationInput(op0, op1, defs0.getTypes().getSchemas(), defs1.getTypes().getSchemas());
                        } else {
                            parametersDiff = CompareRPC.compareOperationInput(op0, op1);
                        }

                        //si hubo cambios a nivel de los parametros.
                        if (parametersDiff != null && parametersDiff.size() > 0) {
                            wsdlDiff.addOperation(new OperationDiff(op1.getQName().getLocalPart(), parametersDiff, OperationDiff.Modification.ONLY_PARAMETER));
                        } else {
                            if (logger.isLoggable(Level.FINE)) {
                                logger.log(Level.FINE, "No change in Operation --> {0}", op1.getQName().getLocalPart());
                            }
                        }
                    } else {
                       unProcessedWsdl1.add(op1.getQName().getLocalPart());
                    }
                }
                
                //COMPARE OPERATION WITH CHANGE IN NAME OPERATION (RENAME OPERATIONS)
                Iterator<String> it = unProcessedWsdl1.iterator();
                while(it.hasNext()){
                    String newOperationName = it.next();
                    Operation op1 = wsdl_bind1.getBindingOperation(newOperationName).getOperation();

                    for (BindingOperation op_bind0 : wsdl_bind0.getBindingOperations()){
                        Operation aux = op_bind0.getOperation();
                        //si la operacion del wsdl0 no fue procesada aun, comparo los parametros de entrada y salida
                        if (!processedWsdl0.contains(aux.getQName().getLocalPart())){
                            boolean equals;
                            //comparo si son iguales los parametros de entrada y de salida
                            if (isDocumentStyle) {
                                equals = CompareDocument.equalsParts(aux.getInput(), op1.getInput(), defs0.getTypes().getSchemas(), defs1.getTypes().getSchemas()) && 
                                         CompareDocument.equalsParts(aux.getOutput(), op1.getOutput(), defs0.getTypes().getSchemas(), defs1.getTypes().getSchemas());
                            }else{
                                equals = CompareRPC.equalsParts(aux.getInput(), op1.getInput()) && CompareRPC.equalsParts(aux.getOutput(), op1.getOutput());
                            }
                            
                            //si son iguales los parametros de entrada y de salida, el la operacion no fue procesada entonces es un rename.
                            if(equals){
                                it.remove();
                                String oldName = aux.getQName().getLocalPart();
                                processedWsdl0.add(oldName);
                                wsdlDiff.addOperation(new OperationDiff(oldName, newOperationName, OperationDiff.Modification.RENAME));
                                if (logger.isLoggable(Level.FINE)) {
                                    logger.log(Level.FINE, "Operation Rename --> {0} for {1}", new Object[]{oldName, newOperationName});
                                }
                                break;
                            }
                        }
                    }
                }
                
                //LIST OF NEW OPERATIONS
                for(String operationName : unProcessedWsdl1){
                    wsdlDiff.addOperation(new OperationDiff(operationName, OperationDiff.Modification.NEW));
                    if (logger.isLoggable(Level.FINE)) {
                        logger.log(Level.FINE, "New Operation --> {0}", operationName);
                    }
                }
            }
        } catch (Exception ex) {
            logger.log(Level.SEVERE, "Exception in compareDefinitions.", ex);
        }
        return wsdlDiff;
    }
    
    private boolean isDocumentStyle(String strWsdl) {
        return strWsdl.matches(EXPR_STYLE_DOCUMENT);
    }
    
}
